"""
Model used to develop our CasseBras contest web application.
"""
from django.db import models
from django.db.models.signals import post_save, post_delete

from django.utils.translation import ugettext_lazy as _

class Category  (models.Model):
    """
    Represent a category to which a Competitor will belong to.
    """
    name            = models.CharField(verbose_name=_("Name"), blank=True, max_length=200)

    def __unicode__(self):
        """
        How to display a Category
        """
        return self.name
        
    def rank_competitors(self):
        """
        Return the list of competitors in this category sorted by score.
        """
        return self.competitor_set.all().order_by('-score')
    
    @models.permalink
    def get_absolute_url(self):
        """
        Return the URL which represent this category
        """
        return ('category', (), {'object-id' : self.id})

class Slope(models.Model):
    """
    Represent a Slope that will be climbed by Competitors.
    Every Competitors independently to his category will be able 
    to climb a Slope.
    Score of the Slope will depend of the number of Competitor who 
    reach the top.
    """
    INITIAL_SCORE   = 1000
    number            = models.IntegerField( verbose_name=_("Number"), 
                                        blank=True, unique=True, null=False)
    initial_score   = models.IntegerField(  verbose_name=_("Initial Score"),
                                            blank=True, null=False, 
                                            default=INITIAL_SCORE)
    score           = models.FloatField(    verbose_name=_("Score"),
                                            default=INITIAL_SCORE)

    def __unicode__(self):
        """
        How to display a Slope
        """
        return unicode(self.number)
    
    def get_score(self):
        """
        Return the score of the Slope.
        It is function of the number of Competitors who reach the top
        """
        try:
            return self.initial_score/len(self.competitor_set.all())
        except ZeroDivisionError:
            return self.initial_score
            
    def update_score(self):
        """
        Compute and save new score to score property then save to database
        """
        Slope.objects.all().filter(id=self.id).update(score=self.get_score())
    
    @models.permalink
    def get_absolute_url(self):
        """
        Return the URL which represent this category
        """
        return ('slope', (), {'object_id': self.id})

class Competitor(models.Model):
    """
    Represent a Competitor/Climber of the contest.
    Each Competitor belong to one Category and only one.
    Competitor will climb slopes, their score will be the sum of the score
    of the Slopes they climb.
    """
    name            = models.CharField( verbose_name=_("Name"),
                                        blank=True, max_length=200)
    surname         = models.CharField( verbose_name=_("Surname"),
                                        blank=True, max_length=200)
    climbed_slopes  = models.ManyToManyField(   Slope, 
                                                verbose_name=_("Successfull Slopes"),
                                                through='SuccessfullClimb')
    category        = models.ForeignKey(Category,
                                        verbose_name=_("Category"))
    score           = models.FloatField(default=0, verbose_name=_("Score"))
    number          = models.IntegerField(unique=True, verbose_name=_("Number"))
    
    def __unicode__(self):
        """
        How to display a Competitor
        """
        name = ' '.join(self.name.split())
        surname = ' '.join(self.surname.split())
        return ' '.join([name, surname])
    
    def update_score(self):
        """
        Score of the Competitor.
        It's the sum of the score of the Slope the competitor climb.
        """
        self.score = sum([slope.get_score() for slope in 
                                                self.climbed_slopes.all()])
        Competitor.objects.all().filter(id=self.id).update(score=self.score)
    
    def clear(self):
        """
        Set Score to 0 by removing all the slopes climbed by the competitor
        """
        for _slope in self.climbed_slopes.all():
            SuccessfullClimb.objects.get(   slope=_slope, 
                                            competitor=self
                                        ).delete()        
        
    def update_climbed_slopes(self, climbed_slopes):
        """
        Assigns climbed_slopes to Competitor.
        Add non existing slopes to Competitor and remove existing ones that
        do not appear in climbed_slopes anymore.
        """
        for _slope in climbed_slopes:
            if _slope not in self.climbed_slopes.all():
                climbed = SuccessfullClimb(  competitor=self, 
                                             slope=_slope)
                climbed.save()
        for _slope in self.climbed_slopes.all():
            if _slope not in climbed_slopes:
                SuccessfullClimb.objects.get(   slope=_slope, 
                                                competitor=self
                                            ).delete()
    
    @models.permalink
    def get_absolute_url(self):
        """
        Return the URL which represent this category
        """
        return ('competitor', (), {'object-id' : self.id})

class SuccessfullClimb(models.Model):
    """
    Intermediate model between Competitor and Slope used to update
    the score of Slope and Competitor linked to the slope
    every time a new association is done.
    """
    competitor  = models.ForeignKey(Competitor, verbose_name=_("Competitor"))
    slope       = models.ForeignKey(Slope, verbose_name=_("Slope"))
    
    class Meta:
        """
        Meta Class used to force SuccessfullClimb behavior.
        """
        # We don't want to associate the same slope to the same competitor
        # several time
        unique_together = (("competitor", "slope"),)
        
    def __unicode__(self):
        return ' <-> '.join([unicode(self.competitor), unicode(self.slope)])

class Contest   (models.Model):
    """
    Represent the Contest with all the existing Category -> All the Competitors
    """
    categories  = models.ManyToManyField(Category)
    #slopes = models.ManyToManyField(Slope)

def _update_score_callback(sender, instance, **kwargs):
    """
    Callback method that will be pass to post_save and post_delete
    signals associated to SuccessfullClimb to update score
    when a new SuccessfullClimb is saved or deleted
    """
    instance.competitor.update_score()
    instance.slope.update_score()
    map(Competitor.update_score, [sc.competitor for sc in \
        SuccessfullClimb.objects.filter(slope=instance.slope)])
    
post_save.connect(_update_score_callback, sender=SuccessfullClimb)
post_delete.connect(_update_score_callback, sender=SuccessfullClimb)
